package com.autumn.platform.web.request;

import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import com.autumn.platform.core.context.reqeust.IRequest;
import com.autumn.platform.core.context.reqeust.IRequestLog;
import com.autumn.platform.core.context.reqeust.RequestContextHolder;
import com.autumn.platform.core.context.token.TokenHolder;
import com.autumn.platform.core.context.track.Tracker;
import com.autumn.platform.dao.page.PageableHolder;
import com.autumn.platform.web.WebHolder;
import com.autumn.platform.web.jsonbody.JsonBodySupport;
import com.autumn.platform.web.request.impl.WebRequest;

@SuppressWarnings("deprecation")
public class RequestContextFilter extends OncePerRequestFilter {

    private boolean threadContextInheritable = false;

    private List<ISessionInvalidateListener> sessionInvalidateListeners;

    private List<IRequestLog> requestLogs;

    public void setThreadContextInheritable(boolean threadContextInheritable) {
        this.threadContextInheritable = threadContextInheritable;
    }

    public void setSessionInvalidateListeners(List<ISessionInvalidateListener> sessionInvalidateListeners) {
        this.sessionInvalidateListeners = sessionInvalidateListeners;
    }

    public void setRequestLogs(List<IRequestLog> requestLogs) {
        this.requestLogs = requestLogs;
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        super.setServletContext(servletContext);
        WebHolder.setApplication(servletContext);
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest req, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Tracker.start();
        HttpServletRequest request = getHttpServletRequest(req);
        LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
        WebRequest webRequest = new WebRequest(request, response);
        RequestContextHolder.setRequest(webRequest, this.threadContextInheritable);
        if (logger.isDebugEnabled()) {
            logger.debug("Bound request info [" + webRequest.getRequestId() + " : " + webRequest.getHttpMethod() + " " + webRequest.getRequestUrl() + "] context to thread: " + request);
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            log();
            LocaleContextHolder.resetLocaleContext();
            PageableHolder.resetPageable();
            JsonBodySupport.remove();
            TokenHolder.remove();
            RequestContextHolder.resetRequest();
            Tracker.stop();
            if (logger.isDebugEnabled()) {
                logger.debug("Cleared thread-bound request info [" + webRequest.getRequestId() + " : " + webRequest.getHttpMethod() + " " + webRequest.getRequestUrl() + "] context: " + request);
            }
        }
    }

    private void log() {
        if (null != this.requestLogs && !this.requestLogs.isEmpty()) {
            IRequest request = RequestContextHolder.getRequest();
            Date date = new Date();
            for (IRequestLog requestLog : requestLogs) {
                requestLog.log(request, date);
            }
        }
    }

    private HttpServletRequest getHttpServletRequest(HttpServletRequest request) {
        return new SessionRequestWrapper(request);
    }

    private class SessionRequestWrapper extends HttpServletRequestWrapper {

        public SessionRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public HttpSession getSession(boolean create) {
            HttpSession session = super.getSession(create);
            if (null != session && !(session instanceof SessionWrapper)) {
                return new SessionWrapper(session);
            }
            return session;
        }

        @Override
        public HttpSession getSession() {
            HttpSession session = super.getSession();
            if (null != session && !(session instanceof SessionWrapper)) {
                return new SessionWrapper(session);
            }
            return session;
        }

    }

    public class SessionWrapper implements HttpSession {

        private final HttpSession delegate;

        private SessionWrapper(HttpSession delegate) {
            this.delegate = delegate;
        }

        @Override
        public long getCreationTime() {
            return delegate.getCreationTime();
        }

        @Override
        public String getId() {
            return delegate.getId();
        }

        @Override
        public long getLastAccessedTime() {
            return delegate.getLastAccessedTime();
        }

        @Override
        public ServletContext getServletContext() {
            return delegate.getServletContext();
        }

        @Override
        public void setMaxInactiveInterval(int interval) {
            delegate.setMaxInactiveInterval(interval);
        }

        @Override
        public int getMaxInactiveInterval() {
            return delegate.getMaxInactiveInterval();
        }

        @Override
        public HttpSessionContext getSessionContext() {
            return delegate.getSessionContext();
        }

        @Override
        public Object getAttribute(String name) {
            return delegate.getAttribute(name);
        }

        @Override
        public Object getValue(String name) {
            return delegate.getValue(name);
        }

        @Override
        public Enumeration<String> getAttributeNames() {
            return delegate.getAttributeNames();
        }

        @Override
        public String[] getValueNames() {
            return delegate.getValueNames();
        }

        @Override
        public void setAttribute(String name, Object value) {
            delegate.setAttribute(name, value);
        }

        @Override
        public void putValue(String name, Object value) {
            delegate.putValue(name, value);
        }

        @Override
        public void removeAttribute(String name) {
            delegate.removeAttribute(name);
        }

        @Override
        public void removeValue(String name) {
            delegate.removeValue(name);
        }

        @Override
        public void invalidate() {
            delegate.invalidate();
            if (null != sessionInvalidateListeners) {
                for (ISessionInvalidateListener sessionInvalidateListener : sessionInvalidateListeners) {
                    sessionInvalidateListener.onInvalidate(delegate);
                }
            }
        }

        @Override
        public boolean isNew() {
            return delegate.isNew();
        }
    }
}
